#include "zen_game_action.h"
#include <vector>
#include <mutex>

namespace Zen
{
	namespace Game
	{
		void ActionDispatcher::OnUpdate(float interval)
		{
			mRunningSeconds += interval;

			while (mActionsRunning.size())
			{
				auto i = mActionsRunning.front();

				if (!i->valid)
				{
					mActionsRunning.pop_front();
					continue; /// deleted i;
				}

				if (i->next_time > mRunningSeconds) break; /// not the time.

				auto Delay = mRunningSeconds - i->time;

				/// in `Run()` the StopAction may be called, so keep the action in queue, ...
				auto next = i->action->Run((float)Delay);

				/// ... pop after `action->Run`
				mActionsRunning.pop_front();

				/**
				 here check valid again, when action->Run() the StopAction() maybe be called.
				 */
				if (i->valid && next >= 0) // running
				{
					i->time = mRunningSeconds;
					// interval should larger than 0, otherwise a dead circle happended.
					i->next_time = mRunningSeconds + std::max((double)next, 1 / 120.0);

					PushRuntimeQueue_(i);
				}
				/// else delete i;
			}

			for (auto i : mActionsNew)
			{
				if (!i->valid) continue;

				auto Delay = i->action->Start();

				if (Delay >= 0 && i->valid)
				{
					i->time = mRunningSeconds;

					i->next_time = mRunningSeconds + Delay; // here Delay can be 0.

					PushRuntimeQueue_(i);
				}
			}
			mActionsNew.clear();
		}
		void ActionDispatcher::PushRuntimeQueue_(std::shared_ptr<ActionRuntimeStatus> item)
		{
			for (auto iter = mActionsRunning.begin(); iter != mActionsRunning.end(); ++iter)
			{
				if ((*iter)->next_time >= item->next_time)
				{
					mActionsRunning.insert(iter, item);
					return;
				}
			}
			mActionsRunning.insert(mActionsRunning.end(), item);
		}

		bool ActionDispatcher::RunAction(std::shared_ptr<Action> action, std::shared_ptr<Node> target)
		{
			if (!action || !target) return false;

			auto i = std::make_shared<ActionRuntimeStatus>();
			i->target = target;
			i->action = action;
			mActionsNew.push_back(i);
			return true;
		}
		int ActionDispatcher::StopAction(std::shared_ptr<Action> action)
		{
			if (!action) return 0;

			int count = 0;
			for (auto i : mActionsRunning)
			{
				if (i->action == action && i->valid)
				{
					i->valid = false;
					++count;
				}
			}
			for (auto& i : mActionsNew)
			{
				if (i->action == action && i->valid)
				{
					i->valid = false;
					++count;
				}
			}
			return count;
		}

		void ActionDispatcher::StopAllActions()
		{
			for (auto i : mActionsNew)
			{
				i->valid = false;
			}
			for (auto i : mActionsRunning)
			{
				i->valid = false;
			}
		}

		int ActionDispatcher::StopActionsOnNode(std::shared_ptr<Node> node)
		{
			int count = 0;
			for (auto i : mActionsRunning)
			{
				if (i->target.lock() == node && i->valid)
				{
					i->valid = false;
					++count;
				}
			}
			for (auto& i : mActionsNew)
			{
				if (i->target.lock() == node && i->valid)
				{
					i->valid = false;
					++count;
				}
			}
			return count;
		}

#define ACTION_STOP (-1.f)

		float ActionCall::Run(float Delay)
		{
			if (!mCall || !mCall(Delay, mRunTimes)) return ACTION_STOP;
			++mRunTimes;
			if (mTimes > 0 && mRunTimes >= mTimes) return ACTION_STOP;
			return mInterval;
		}
		float ActionCall::Start()
		{
			mRunTimes = 0;
			return mDelay;
		}

		float ActionTimed::Start()
		{
			mAge = 0;
			return mDelay;
		}

		float ActionTimed::Run(float interval)
		{
			mAge += interval;

			if (mLifeTime >= 0 && mAge >= mLifeTime)
			{
				mAge = mLifeTime;
				return ACTION_STOP;
			}
			return mInterval;
		}

		/*
		void PartActionView2D::initTarget(std::shared_ptr<NodeWithMatrix2DPass> target)
		{
			mTarget = target;
		}

			// action move

		float ActionMove2D::Start() {
			if(mTarget.expired()) return ACTION_STOP;

			m_pos_var = { 0, 0 };
			return ActionTimed::Start();
		}
		void ActionMove2D::initMove(Point2 speed, Point2 acc)
		{
			m_speed = speed;
			m_acc = acc;
		}
		float ActionMove2D::Run(float interval)
		{
			if(mTarget.expired())
				return false;

			auto age = GetAge();
			float x = m_speed.x * age + 0.5f * age * age * m_acc.x;
			float y = m_speed.y * age + 0.5f * age * age * m_acc.y;

			auto target = GetTarget();
			target->SetPositionX(target->GetX() + x - m_pos_var.x);
			target->SetY(target->GetY() + y - m_pos_var.y);

			m_pos_var = { x, y };
			return true;
		}

			// action scale

		void ActionScale2D::initScale(Point2 speed, Point2 acc)
		{
			m_speed = speed;
			m_acc = acc;
			m_scale_var = { 0, 0 };
		}

		float ActionScale2D::Run(float interval)
		{
			if(mTarget.expired())
				return ACTION_STOP;

			auto ret = ActionTimed::Run(interval);

			auto age = GetAge();

			float x = m_speed.x * age + 0.5f * age * age * m_acc.x;
			float y = m_speed.y * age + 0.5f * age * age * m_acc.y;

			auto target = GetTarget();
			target->SetScaleX(target->GetScaleX() + x - m_scale_var.x);
			target->SetScaleY(target->GetScaleY() + y - m_scale_var.y);
			m_scale_var = {x, y};

			return ret;
		}

		float ActionScale2D::Start()
		{
			if(mTarget.expired()) return ACTION_STOP;
			m_scale_var = { 0, 0 };
			return ActionTimed::Start();
		}

			// action rotate

		void ActionRotate2D::initRotate(float velo, float acc)
		{
			m_angular_speed = velo;
			m_acc = acc;
			m_angular_var = 0;
		}
		float ActionRotate2D::Run(float interval)
		{
			if(mTarget.expired()) return ACTION_STOP;

			auto ret = ActionTimed::Run(interval);
			auto age = GetAge();
			auto r = m_angular_speed * age + 0.5f * age * age * m_acc;
			auto target = GetTarget();
			target->SetRotation(target->GetRotation() + r - m_angular_var);
			m_angular_var = r;
			return ret;
		}
		float ActionRotate2D::Start()
		{
			if(mTarget.expired()) return ACTION_STOP;

			m_angular_var = 0;
			return ActionTimed::Start();
		}
		*/

		// action repeat

		void ActionRepeat::Init(std::shared_ptr<Action> action, int times)
		{
			mAction = action;
			mTimes = times;
		}

		float ActionRepeat::Run(float interval)
		{
			if (!mAction) return ACTION_STOP;

			if (!mIsInnerRun)
			{
				mIsInnerRun = true;
				return mAction->Start(); // if start failed, stop repeat.
			}

			auto res = mAction->Run(interval);
			if (res >= 0) return res;

			++mRunTimes;

			if (mTimes > 0 && mRunTimes >= mTimes)
			{
				return ACTION_STOP;
			}

			mIsInnerRun = false;
			return mInterval;
		}

		float ActionRepeat::Start()
		{
			mRunTimes = 0;
			mIsInnerRun = false;
			return mDelay;
		}

		// action sequence

		void ActionSequence::AddAction(std::shared_ptr<Action> action)
		{
			mActions.push_back(action);
		}

		float ActionSequence::Start()
		{
			if (mActions.empty()) return ACTION_STOP;
			mIndex = 0;
			mIsInnerRun = false;
			return mDelay;
		}
		float ActionSequence::Run(float interval)
		{
			if (mIndex >= mActions.size()) return ACTION_STOP;

			auto action = mActions[mIndex];

			if (!mIsInnerRun)
			{
				auto res = action->Start();
				if (res >= 0)
				{
					mIsInnerRun = true;
					return res;
				}
			}
			else
			{
				auto res = action->Run(interval);
				if (res >= 0) return res;
				mIsInnerRun = false;
			}
			++mIndex;
			return (mIndex < mActions.size() ? mInterval : ACTION_STOP);
		}

		void ActionSequence::InitActions(std::vector<std::shared_ptr<Action>> actions)
		{
			mActions.swap(actions);
		}

		// Generate
		std::shared_ptr<Action> ActionCall::Generate(Function call, int times, float Delay, float interval)
		{
			auto act = std::make_shared<ActionCall>();

			act->Init(call, times);

			act->SetDelay(Delay);
			act->SetInterval(interval);

			return act;
		}

		void ActionCall::Init(Function call, int times)
		{
			this->SetCallFunc(call);
			this->SetRepeatTimes(times);
		}

		/*
		std::shared_ptr<Action> ActionMove2D::
		Generate(std::shared_ptr<NodeWithMatrix2DPass> target, float Duration,
			   Point2 speed, Point2 acc, float Delay, float interval)
		{
			auto act = std::make_shared<ActionMove2D>();

			act->initTarget(target);
			act->initMove(speed, acc);

			act->SetLifetime(Duration);

			act->SetDelay(Delay);
			act->SetInterval(interval);
			return act;
		}
		std::shared_ptr<Action> ActionScale2D::
		Generate(std::shared_ptr<NodeWithMatrix2DPass> target, float Duration,
			   Point2 speed, Point2 acc, float Delay, float interval)
		{
			auto act = std::make_shared<ActionScale2D>();
			act->initTarget(target);
			act->SetLifetime(Duration);
			act->initScale(speed, acc);

			act->SetDelay(Delay);
			act->SetInterval(interval);
			return act;
		}

		std::shared_ptr<Action> ActionRotate2D::
		Generate(std::shared_ptr<NodeWithMatrix2DPass> target, float Duration,
			   float angular_speed, float acc, float Delay, float interval)
		{
			auto act = std::make_shared<ActionRotate2D>();
			act->initTarget(target);
			act->SetLifetime(Duration);
			act->initRotate(angular_speed, acc);

			act->SetDelay(Delay);
			act->SetInterval(interval);
			return act;
		}
		 */

		std::shared_ptr<Action> ActionRepeat::Generate(std::shared_ptr<Action> action, int times, float Delay, float interval)
		{
			auto act = std::make_shared<ActionRepeat>();
			act->Init(action, times);

			act->SetDelay(Delay);
			act->SetInterval(interval);
			return act;
		}
		std::shared_ptr<Action> ActionSequence::Generate(std::vector<std::shared_ptr<Action>> actions, float Delay, float interval)
		{
			auto act = std::make_shared<ActionSequence>();
			act->InitActions(actions);

			act->SetDelay(Delay);
			act->SetInterval(interval);
			return act;
		}
	} // namespace Game
} // namespace Zen
